package webjoch.wrappers;

import java.util.LinkedList;
import java.util.List;

import webjoch.Main;
import webjoch.methods.Links;
import webjoch.methods.Rooms;
import webjoch.misc.Constants;
import webjoch.misc.Functions;
import webjoch.process.strategies.Boss;
import webjoch.process.strategies.Puzzle;
import webjoch.wrappers.Door.Rotation;

import com.rsbuddy.script.methods.GroundItems;
import com.rsbuddy.script.methods.Npcs;
import com.rsbuddy.script.methods.Objects;
import com.rsbuddy.script.methods.Players;
import com.rsbuddy.script.util.Filter;
import com.rsbuddy.script.wrappers.Area;
import com.rsbuddy.script.wrappers.GameObject;
import com.rsbuddy.script.wrappers.GroundItem;
import com.rsbuddy.script.wrappers.Npc;
import com.rsbuddy.script.wrappers.Tile;

public class Room {
	private Area area;
	
	public enum Type{
		BASE,
		NORMAL,
		PUZZLE,
		BOSS
	}
	
	public Room(Area area){
		this.area = area;
	}
	
	/**
	 * Checks if the room is valid
	 * @return returns true if the room is valid
	 */
	public boolean isValid(){
		GameObject[] objects = Objects.getLoaded(new Filter<GameObject>(){
			public boolean accept(GameObject o){
				return area.contains(o.getLocation());
			}
		});
		return objects.length > 0;
	}
	
	/**
	 * Gets the location of the room
	 * @return the central tile of the room
	 */
	public Tile getLocation(){
		return area.getCentralTile();
	}
	
	/**
	 * Gets the area of the room
	 * @return the room area
	 */
	public Area getArea(){
		return area;
	}
	
	/**
	 * Gets the room type
	 * @return room type
	 */
	public Type getType(){
		if (getNpc(Constants.SMUGLER) != null) return Type.BASE;
		if (getPuzzle() != null) return Type.PUZZLE;
		if (Boss.isBossRoom(this)) return Type.BOSS;
		return Type.NORMAL;
	}
	
	public int getRotation(){
		if (getType() == Type.BASE) return 1;
		int rotation = Main.getDungeon().getRoomRotation(this);
		if (rotation > -1){
			return rotation;
		}else{
			if (getOpenDoorCount() == 1){
				for (Door door : getDoors()){
					if (door.isOpen()){
						Main.getDungeon().addRoomRotation(this, door.getRotation().getRoomRotation());
						return door.getRotation().getRoomRotation();
					}
				}
			}
		}
		return 1;
	}
	
	/**
	 * Checks if the local player is in the room
	 * @return returns true if the player is in the room
	 */
	public boolean inRoom(){
		return area.contains(Players.getLocal().getLocation());
	}
	
	/**
	 * Get the door based on the given rotation
	 * @param rotation rotation of the door
	 * @return the door
	 */
	public Door getDoor(Door.Rotation rotation){
		try{
			Tile tile1 = getArea().getTileArray()[getArea().getTileArray().length-1];
			Tile tile2 = getArea().getTileArray()[0];
			
			final Tile doorTile =  
			(rotation == Rotation.NORTH) ? new Tile(tile1.getX() - 8, tile1.getY()) : 
			(rotation == Rotation.EAST) ? new Tile(tile1.getX(), tile1.getY() - 8) : 
			(rotation == Rotation.SOUTH) ? new Tile(tile2.getX() + 8, tile2.getY()) :
			(rotation == Rotation.WEST) ? new Tile(tile2.getX(), tile2.getY() + 8) : null;
	
			GameObject door = Objects.getTopAt(doorTile);
			if (door == null) return null;
			return new Door(door);
		}catch(NullPointerException e){
			System.out.println("NullPointerException in door#getDoor");
			return null;
		}
	}
	
	/**
	 * Get all doors in the room
	 * @return an array of doors
	 */
	public Door[] getDoors(){
		List<Door> doors = new LinkedList<Door>();
		GameObject[] objectDoors = Objects.getLoaded(new Filter<GameObject>(){
			public boolean accept(GameObject o){
				if (!area.contains(o.getLocation())) return false;
				return Functions.inArray(Constants.ALL_DOORS, o.getId()) && !Functions.inArray(Constants.KEY_DOORS, o.getId()) || Functions.inArray(Constants.KEY_DOORS, o.getId() - 145);
			}
		});
		for (GameObject objectDoor : objectDoors){
			doors.add(new Door(objectDoor, this));
		}
		
		return doors.toArray(new Door[doors.size()]);
	}
	
	/**
	 * Get the amount of doors in the room (1 - 4)
	 * @return amount of doors
	 */
	public int getDoorCount(){
		Door[] doors = getDoors();
		int doorCount = doors.length;
		for (Door door : doors){
			if (Main.getDungeon().shouldIgnoreDoor(door)) doorCount--;
		}
		return doorCount;
	}
	
	/**
	 * Get the amount of opened doors in the room
	 * @return amount of opened doors
	 */
	public int getOpenDoorCount(){
		int c = 0;
		for(Door door : getDoors()){
			c += door.isOpen() ? 1 : 0;
		}
		return c;
	}
	
	/**
	 * Checks if the room is a guardian room
	 * @return returns true if it is a guardian room
	 */
	public boolean isGuardianRoom(){
		for (Door door : getDoors()){
			if (door.getType() == Door.Type.GUARDIAN) return true;
		}
		return false;
	}
	
	/**
	 * Get all enemies in the room
	 * @return an array of npc`s
	 */
	public Npc[] getEnemies(){
		return Npcs.getLoaded(new Filter<Npc>(){
			public boolean accept(Npc npc){
				if (!area.contains(npc.getLocation())) return false;
				return Functions.inArray(Constants.ENEMIES, npc.getId());
			}
		});
	}
	
	/**
	 * Get all npc`s in the room
	 * @return an array of npc`s
	 */
	public Npc[] getNpcs(){
		return Npcs.getLoaded(new Filter<Npc>(){
			public boolean accept(Npc npc){
				return area.contains(npc.getLocation());
			}
		});
	}
	
	/**
	 * Checks if the room has enemies
	 * @return returns true if there are enemies
	 */
	public boolean containsEnemies(){
		return getEnemies().length > 0;
	}
	
	/**
	 * Get the dropped key in the door
	 * @return give the key or null if there isn't one
	 */
	public GroundItem getKey(){
		return getGroundItem(Constants.ALL_KEYS);
	}
	
	/**
	 * Check if it is possible to open one or more doors
	 * @return returns true if possible
	 */
	public boolean canOpenDoors(){
		for (Door door : getDoors()){
			if(door.canOpen()){
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Update all links from the doors in the room
	 */
	public void updateLinks(){
		//Main.getDungeon().removeRoom(this);
		Link link;
		search:
		for (Door door : getDoors()){
			for (Link link1 : Links.getRoomLinks(this)){
				if (link1.getRotation().equals(door.getRotation())){
					continue search;
				}
			}
			if (!door.isOpen() || door == null) continue;
			link = new Link(this, door.getRotation(), Rooms.getRoom(door.getRoomTileBehindDoor()));
			Main.getDungeon().addRoom(link);
		}
	}
	
	/**
	 * Get the location of the player in the room
	 * @return returns an int array: 0=x, 1=y
	 */
	public int[] getLocalLocation(boolean includeRotation){
		Tile firstTile = area.getTileArray()[0];
		Tile localTile = Players.getLocal().getLocation();
		int x = localTile.getX() - firstTile.getX();
		int y = localTile.getY() - firstTile.getY();
		if (includeRotation && getRotation() != 1){
			switch(getRotation()){
			case 0: return new int[] {y, 14 - x + 1};
			case 2: return new int[] {14 - y + 1, x};
			case 3: return new int[] {14 - x + 1, 14 - y + 1};
			}
		}
		return new int[] {x, y};
	}
	
	/**
	 * Gets the puzzle in the room
	 * @return the puzzle or null if there isn't one
	 */
	public Puzzle getPuzzle(){
		for (Puzzle puzzle : Main.getPuzzles()){
			puzzle.setRoom(this);
			if (puzzle.isValid()) return puzzle;
		}
		return null;
	}
	public Boss getBoss(){
	    for (Boss boss : Main.getBosses()) {
	        boss.setRoom(this);
	        if (boss.isValid()) return boss;
	    }
	    return null;
	}
	
	/**
	 * Get the nearest object in the room
	 * @param id the object id to look for
	 * @return the object
	 */
	public GameObject getObject(final int id){
		return Objects.getNearest(new Filter<GameObject>(){
			public boolean accept(GameObject o) {
				return getArea().contains(o.getLocation()) && o.getId() == id;
			}
		});
	}
	
	/**
	 * Get the nearest object in the room
	 * @param id the object id to look for
	 * @return the object
	 */
	public GameObject getObject(final int ids[]){
		return Objects.getNearest(new Filter<GameObject>(){
			public boolean accept(GameObject o) {
				return getArea().contains(o.getLocation()) && Functions.inArray(ids, o.getId());
			}
		});
	}
	
	/**
	 * Get the nearest NPC in the room
	 * @param id the npc id to look for
	 * @return the NPC
	 */
	public Npc getNpc(final int id){
		return Npcs.getNearest(new Filter<Npc>(){
			public boolean accept(Npc o) {
				return getArea().contains(o.getLocation()) && o.getId() == id;
			}
		});
	}
	
	/**
	 * Get the nearest NPC in the room
	 * @param id the npc id`s to look for
	 * @return the NPC
	 */
	public Npc getNpc(final int id[]){
		return Npcs.getNearest(new Filter<Npc>(){
			public boolean accept(Npc o) {
				return getArea().contains(o.getLocation()) && Functions.inArray(id, o.getId());
			}
		});
	}
	
	/**
	 * Get the nearest GroundItem in the room
	 * @param id the GroundItem id to look for
	 * @return the GroundItem
	 */
	public GroundItem getGroundItem(final int id){
		return GroundItems.getNearest(new Filter<GroundItem>(){
			public boolean accept(GroundItem o) {
				return getArea().contains(o.getLocation()) && o.getItem().getId() == id;
			}
		});
	}
	
	/**
	 * Get the nearest GroundItem in the room
	 * @param id the GroundItem id`s to look for
	 * @return the GroundItem
	 */
	public GroundItem getGroundItem(final int[] ids){
		return GroundItems.getNearest(new Filter<GroundItem>(){
			public boolean accept(GroundItem o) {
				return getArea().contains(o.getLocation()) && Functions.inArray(ids, o.getItem().getId());
			}
		});
	}
	
	/**
	 * Get the nearest GroundItem in the room
	 * @param id the GroundItem id`s to look for
	 * @return the GroundItem
	 */
	public GroundItem getGroundItem(final int[][] ids){
		return GroundItems.getNearest(new Filter<GroundItem>(){
			public boolean accept(GroundItem o) {
				return getArea().contains(o.getLocation()) && Functions.inArray(ids, o.getItem().getId());
			}
		});
	}
}











